#pragma once

#include "decision.h"

namespace solver {

	class literal_node;

	typedef skip_lists::skip_list_type<literal, literal_node *, literal_compare_type> literal_node_list;
	typedef skip_lists::skip_list_type<literal, literal_node *, literal_compare_type>::node_type literal_node_node;

	class literal_node
	{
	public:

		literal_node *parent;
		literal lit;

		// std::list<literal_node *> children;
		literal_node_list children;
		int string_id;
		bool used;

		literal_node() :
			parent(nullptr),
			string_id(-1),
			used(false),
			children(0, nullptr)
		{
			lit.ref = -1;
			lit.val = 0;
		}

		literal_node(literal_node *_parent, literal _lit, int _string_id)
			: parent(_parent),
			lit(_lit),
			string_id(_string_id),
			used(false),
			children(0, nullptr)
		{
			;
		}

		~literal_node()
		{
			;
		}

		decision_string to_string()
		{
			decision_string ds;
			literal_node *p = this;
			while (p->parent != nullptr)
			{
				ds.add(p->lit);
				p = p->parent;
			}
			return ds;
		}
	};


	class decision_string_collection
	{
		int string_id;
		literal_node *root;

		bool find_strings(literal_node *n, decision_string& dstr, std::list<literal_node *>& found_strings)
		{
			bool result = false;

			for (auto childnode = n->children.first(); childnode != nullptr; childnode = n->children.next(childnode))
			{
				auto child = childnode->value;
				if (dstr.exists_exact(child->lit))
				{
					if (child->string_id)
					{
						found_strings.push_back(child);
						result = true;
						find_strings(child, dstr, found_strings);
					}
					else
					{
						bool temp;
						temp = find_strings(child, dstr, found_strings);
						if (temp)
						{
							result = true;
						}
					}
				}
			}

			return result;
		}

		int add(literal_node *n, decision_string& ds, decision_node *dsn)
		{
			int result = 0;
			if (dsn == nullptr)
			{
				string_id++;
				n->string_id = string_id;
				return string_id;
			}
			const literal& target = dsn->value;
			bool found = false;

			auto found_node = n->children.find(target);

			if (found_node)
			{
				found = true;
				auto dsnn = ds.next(dsn);
				result = add(found_node->value, ds, dsnn);
			}

			if (!found)
			{
				literal_node *new_node = new literal_node(n, target, false);
				n->children.update(target, new_node);
				auto dsnn = ds.next(dsn);
				result = add(new_node, ds, dsnn);
				count++;
			}

			return result;
		}

		void clear(literal_node *n)
		{
			for (auto childnode = n->children.first(); childnode != nullptr; childnode = n->children.next(childnode))
			{
				clear(childnode->value);
			}
			n->children.clear();
			delete n;
		}

		int count;

	public:

		decision_string_collection() : count(0), string_id(0)
		{
			root = new literal_node();
		}

		virtual ~decision_string_collection()
		{
			if (root != nullptr)
			{
				clear(root);
				root = nullptr;
			}
			count = 0;
		}

		void clear()
		{
			string_id = 0;
			clear(root);
			root = new literal_node();
		}

		bool find_strings(decision_string& ds, std::list<literal_node *>& found_strings)
		{
			bool result;
			result = find_strings(root, ds, found_strings);
			return result;
		}

		int add(decision_string& ds)
		{
			int sid = 0;
			sid = add(root, ds, ds.first());
			return sid;
		}

		int get_count()
		{
			return count;
		}

		static void self_test(std::list<std::string>& results)
		{
			decision_string_collection collection;

			collection.self_test_impl(results);
		}

		void self_test_impl(std::list<std::string>& results)
		{

			decision_string rule1("ABC");
			decision_string rule2("A!BF");
			decision_string rule3("CD");
			decision_string rule4("!ABD");
			decision_string rule5("!B!E");

			add(rule1);
			add(rule2);
			add(rule3);
			add(rule4);
			add(rule5);

			decision_string test1("!ACE");
			decision_string test2("ABCD");
			decision_string test3("A!BC!E");

			std::list<literal_node *> fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9;

			if (!find_strings(rule1, fn1))
				results.push_back("decision_string_collection: rule1 invalid");

			if (!find_strings(rule2, fn2))
				results.push_back("decision_string_collection: rule2 invalid");

			if (!find_strings(rule3, fn3))
				results.push_back("decision_string_collection: rule2 invalid");

			if (!find_strings(rule3, fn3))
				results.push_back("decision_string_collection: rule2 invalid");

			if (!find_strings(rule4, fn4))
				results.push_back("decision_string_collection: rule4 invalid");

			if (!find_strings(rule5, fn5))
				results.push_back("decision_string_collection: rule5 invalid");

			// now, should be able to find 2 & 3 but not find 1
			if (find_strings(test1, fn6))
				results.push_back("decision_string_collection: test1 invalid");

			if (!find_strings(test2, fn7))
				results.push_back("decision_string_collection: test2 invalid");

			if (!find_strings(test3, fn8))
				results.push_back("decision_string_collection: test3 invalid");

		}
	};

	class production
	{
	public:
		decision_string match;
		literal product;
		int stringid;
		pdecision source_clause;
	};

	typedef production *pproduction;

	class decision_result
	{
	public:
		bool matched;
	};


}
